// 线程同步：锁、Condvar 和信号量
// 当你需要同时访问一个资源、控制不同线程的执行次序时，都需要使用到同步性。

/*
该如何选择
共享内存可以说是同步的灵魂，因为消息传递的底层实际上也是通过共享内存来实现，两者的区别如下：
    共享内存相对消息传递能节省多次内存拷贝的成本
    共享内存的实现简洁的多
    共享内存的锁竞争更多
消息传递适用的场景很多，我们下面列出了几个主要的使用场景:
    需要可靠和简单的(简单不等于简洁)实现时
    需要模拟现实世界，例如用消息去通知某个目标执行相应的操作时
    需要一个任务处理流水线(管道)时

使用共享内存(并发原语)的场景往往就比较简单粗暴：需要简洁的实现以及更高的性能时

消息传递类似一个单所有权的系统：一个值同时只能有一个所有者，如果另一个线程需要该值的所有权，需要将所有权通过消息传递进行转移。
而共享内存类似于一个多所有权的系统：多个线程可以同时访问同一个值。
*/

// 互斥锁 Mutex
#[cfg(test)]
mod mutex {
    use std::sync::{Arc, Mutex};
    use std::thread;

    #[test]
    fn mutex_base() {
        // 使用`Mutex`结构体的关联函数创建新的互斥锁实例
        let m = Mutex::new(5);

        {
            // 获取锁，然后deref为`m`的引用
            // lock返回的是Result
            let mut num = m.lock().unwrap(); // m.lock()方法也有可能报错，例如当前正在持有锁的线程panic
            *num = 6;
            // 锁自动被drop
        };
        /*
        Mutex<T>是一个智能指针，准确的说是m.lock()返回一个智能指针MutexGuard<T>:
            它实现了Deref特征，会被自动解引用后获得一个引用类型，该引用指向Mutex内部的数据
            它还实现了Drop特征，在超出作用域后，自动释放锁，以便其它线程能继续获取锁
        */

        // 和Box类似，数据被Mutex所拥有
        // 要访问内部的数据，需要使用方法m.lock()向m申请一个锁, 该方法会阻塞当前线程，直到获取到锁
        println!("m = {:?}", m);
    }

    #[test]
    fn mutex_block() {
        let m = Mutex::new(5);

        let mut num = m.lock().unwrap();
        *num = 6;
        // 锁还没有被 drop 就尝试申请下一个锁，导致主线程阻塞
        drop(num);
        let mut num1 = m.lock().unwrap();
        *num1 = 7;
        // drop(num1); // 手动 drop num1 ，观察打印结果的不同

        // drop,undrop  m = Mutex { data: <locked>, poisoned: false, .. }
        // drop * 2     m = Mutex { data: 7, poisoned: false, .. }
        println!("m = {:?}", m);
    }

    #[test]
    fn mutex_rc() {
        // 通过`Rc`实现`Mutex`的多所有权 ,Rc<T>无法在线程中传输，因为它没有实现Send特征
        // let counter = Rc::new(Mutex::new(0));

        // Arc它的内部计数器是多线程安全的，因此可以在多线程环境中使用
        let counter = Arc::new(Mutex::new(0));

        let mut handles = vec![];

        for _ in 0..10 {
            // let counter = Rc::clone(&counter);
            let counter = Arc::clone(&counter);
            // 创建子线程，并将`Mutex`的所有权拷贝传入到子线程中
            let handle = thread::spawn(move || {
                let mut num = counter.lock().unwrap();
                *num += 1;

                // 在多线程中计数的功能，由于多个线程都需要去修改该计数器，
                // 需要使用锁来保证同一时间只有一个线程可以修改计数器
            });

            handles.push(handle);
        }

        // 等待所有子线程完成
        for handle in handles {
            handle.join().unwrap();
        }

        // 输出最终的计数结果
        println!("Result: {}", *counter.lock().unwrap());
    }
}
/*



内部可变性
其中Rc<T>和RefCell<T>的结合，可以实现单线程的内部可变性。
由于Mutex<T>可以支持修改内部数据，当结合Arc<T>一起使用时，可以实现多线程的内部可变性。
简单总结下：
    Rc<T>/RefCell<T>用于单线程内部可变性
    Arc<T>/Mutex<T>用于多线程内部可变性。

需要小心使用的 Mutex
如果有其它语言的编程经验，就知道互斥锁这家伙不好对付，想要正确使用，你得牢记在心：
    在使用数据前必须先获取锁
    在数据使用完成后，必须及时的释放锁，比如文章开头的例子，使用内部语句块的目的就是为了及时的释放锁

这两点看起来不起眼，但要正确的使用，其实是相当不简单的，对于其它语言，忘记释放锁是经常发生的，
虽然 Rust 通过智能指针的drop机制帮助我们避免了这一点，但是由于不及时释放锁导致的性能问题也是常见的。

    正因为这种困难性，导致很多用户都热衷于使用消息传递的方式来实现同步，例如 Go 语言直接把channel内置在语言特性中，甚至还有无锁的语言，例如erlang，完全使用Actor模型，依赖消息传递来完成共享和同步。
    幸好 Rust 的类型系统、所有权机制、智能指针等可以很好的帮助我们减轻使用锁时的负担。

另一个值的注意的是在使用Mutex<T>时，Rust 无法帮我们避免所有的逻辑错误，
    使用Rc<T>可能会导致循环引用的问题。
    Mutex<T>也存在使用上的风险，例如创建死锁(deadlock)：当一个操作试图锁住两个资源，然后两个线程各自获取其中一个锁，并试图获取另一个锁时，就会造成死锁


*/

#[cfg(test)]
mod dead_lock {
    use std::sync::{Mutex, MutexGuard};
    use std::thread;
    use std::thread::sleep;
    use std::time::Duration;

    use lazy_static::lazy_static;

    #[test]
    fn base() {
        let data = Mutex::new(0);
        let d1 = data.lock();
        let d2 = data.lock();
        // 在另一个锁还未被释放时去申请新的锁，就会触发
    } // d1锁在此处释放

    lazy_static! {
        static ref MUTEX1: Mutex<i64> = Mutex::new(0);
        static ref MUTEX2: Mutex<i64> = Mutex::new(0);
    }

    /*
    死锁条件：
        线程 1 锁住了MUTEX1并且线程2锁住了MUTEX2
        然后线程 1 试图去访问MUTEX2，同时线程2试图去访问MUTEX1，就会死锁。
    ！死锁条件：
        线程 2 在线程 1 锁MUTEX1之前，就已经全部执行完了，随之线程 2 的MUTEX2和MUTEX1被全部释放
        线程 1 对锁的获取将不再有竞争者。（反之依然）
    */
    #[test]
    fn main() {
        // 存放子线程的句柄
        let mut children = vec![];
        for i_thread in 0..2 {
            children.push(thread::spawn(move || {
                for _ in 0..1 {
                    // 线程1
                    if i_thread % 2 == 0 {
                        // 锁住MUTEX1
                        let guard: MutexGuard<i64> = MUTEX1.lock().unwrap();

                        println!("线程 {} 锁住了MUTEX1，接着准备去锁MUTEX2 !", i_thread);

                        // 当前线程睡眠一小会儿，等待线程2锁住MUTEX2
                        sleep(Duration::from_millis(10));

                        // 去锁MUTEX2
                        let guard = MUTEX2.lock().unwrap();
                        // 线程2
                    } else {
                        // 锁住MUTEX2
                        let _guard = MUTEX2.lock().unwrap();

                        println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread);

                        let _guard = MUTEX1.lock().unwrap();
                    }
                }
            }));
        }

        // 等子线程完成
        for child in children {
            let _ = child.join();
        }

        println!("死锁没有发生");
    }

    // 与lock方法不同，try_lock会尝试去获取一次锁，如果无法获取会返回一个错误，因此不会发生阻塞
    #[test]
    fn try_lock() {
        // 存放子线程的句柄
        let mut children = vec![];
        for i_thread in 0..2 {
            children.push(thread::spawn(move || {
                for _ in 0..1 {
                    // 线程1
                    if i_thread % 2 == 0 {
                        // 锁住MUTEX1
                        let guard: MutexGuard<i64> = MUTEX1.lock().unwrap();

                        println!("线程 {} 锁住了MUTEX1，接着准备去锁MUTEX2 !", i_thread);

                        // 当前线程睡眠一小会儿，等待线程2锁住MUTEX2
                        sleep(Duration::from_millis(10));

                        // 去锁MUTEX2
                        let guard = MUTEX2.try_lock();
                        println!("线程 {} 获取 MUTEX2 锁的结果: {:?}", i_thread, guard);
                        // 线程2
                    } else {
                        // 锁住MUTEX2
                        let _guard = MUTEX2.lock().unwrap();

                        println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread);
                        sleep(Duration::from_millis(10));
                        // 会报出一个错误:Err("WouldBlock")，接着线程中的剩余代码会继续执行，不会被阻塞。
                        let guard = MUTEX1.try_lock();
                        println!("线程 {} 获取 MUTEX1 锁的结果: {:?}", i_thread, guard);
                    }
                }
            }));
        }

        // 等子线程完成
        for child in children {
            let _ = child.join();
        }

        println!("死锁没有发生");
    }
}
